package com.weishuang.monitor.leader.config.provider;

import com.weishuang.monitor.leader.commons.enums.ICODE;
import com.weishuang.monitor.leader.config.security.CustomUserDetailsService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.security.authentication.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsChecker;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

/**
 * 账号密码
 */
@Component
@Slf4j
public class CustomerAuthenticationProvider implements
        AuthenticationProvider, MessageSourceAware {

	protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
	private UserDetailsChecker preAuthenticationChecks = new DefaultPreAuthenticationChecks();
	private UserDetailsChecker postAuthenticationChecks = new DefaultPostAuthenticationChecks();
	private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper();

	/**
	 * 加密方法
	 */
	@Autowired
	private PasswordEncoder passwordEncoder;

	/**
	 * 用户信息服务
	 */
	@Autowired
	private CustomUserDetailsService userDetailsService;

	/**
	 * 校验账户密码
	 * @param userDetails
	 * @param authentication
	 * @throws AuthenticationException
	 */
	protected void additionalAuthenticationChecks(UserDetails userDetails,
                                                  UsernamePasswordAuthenticationToken authentication)
			throws AuthenticationException {
		if (authentication.getCredentials() == null) {
			log.debug("验证失败:没有提供凭据");
			throw new BadCredentialsException(ICODE.LOGIN_USER_IS_NULL.getMsg());
		}
		String presentedPassword = authentication.getCredentials().toString();
		if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
			log.debug("验证失败:密码与存储值不匹配");
			throw new BadCredentialsException(ICODE.LOGIN_USER_IS_NULL.getMsg());
		}
	}

	/**
	 * 进行登录校验
	 * @param authentication
	 * @return
	 * @throws AuthenticationException
	 */
	public Authentication authenticate(Authentication authentication)
			throws AuthenticationException {
		Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,() -> "只支持UsernamePasswordAuthenticationToken");
		String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED" : authentication.getName();
		UserDetails user;
		try {
			user = retrieveUser(username);
		}catch (UsernameNotFoundException notFound) {
			log.debug("User '" + username + "' not found");
			throw new BadCredentialsException(ICODE.LOGIN_USER_IS_NULL.getMsg());
		}
		try {
			preAuthenticationChecks.check(user);
			additionalAuthenticationChecks(user,(UsernamePasswordAuthenticationToken) authentication);
		}catch (AuthenticationException exception) {
			throw exception;
		}
		//校验是否过期
		postAuthenticationChecks.check(user);
		Object principalToReturn = user;
		return createSuccessAuthentication(principalToReturn, authentication, user);
	}

	/**
	 * 创建登录成功用户证明
	 * @param principal
	 * @param authentication
	 * @param user
	 * @return
	 */
	protected Authentication createSuccessAuthentication(Object principal,
                                                         Authentication authentication, UserDetails user) {
		UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(
				principal, authentication.getCredentials(),
				authoritiesMapper.mapAuthorities(user.getAuthorities()));
		result.setDetails(authentication.getDetails());
		return result;
	}

	/**
	 * 根据用户名获取用户信息，用于校验
	 * @param username
	 * @return	返回用户信息
	 * @throws AuthenticationException
	 */
	protected UserDetails retrieveUser(String username)
			throws AuthenticationException {
		try {
			UserDetails loadedUser = userDetailsService.loadUserByUsername(username);
			if (loadedUser == null) {
				throw new InternalAuthenticationServiceException(ICODE.LOGIN_USER_IS_NULL.getMsg());
			}
			return loadedUser;
		}catch (UsernameNotFoundException ex) {
			throw ex;
		}catch (InternalAuthenticationServiceException ex) {
			throw ex;
		}catch (Exception ex) {
			throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
		}
	}

	public void setMessageSource(MessageSource messageSource) {
		this.messages = new MessageSourceAccessor(messageSource);
	}

	/**
	 * 判断是否使用当前处理器
	 * @param authentication
	 * @return
	 */
	public boolean supports(Class<?> authentication) {
		return (UsernamePasswordAuthenticationToken.class
				.isAssignableFrom(authentication));
	}

	/**
	 * 校验用户账户状态，统一锁定状态
	 */
	private class DefaultPreAuthenticationChecks implements UserDetailsChecker {
		public void check(UserDetails user) {
			if (!user.isAccountNonLocked()) {
				log.debug("用户帐户被锁定");
				throw new LockedException(ICODE.LOGIN_USER_IS_DISABLED.getMsg());
			}
			if (!user.isEnabled()) {
				log.debug("禁用的用户帐户");
				throw new DisabledException(ICODE.LOGIN_USER_IS_DISABLED.getMsg());
			}
			if (!user.isAccountNonExpired()) {
				log.debug("用户帐户过期");
				throw new AccountExpiredException(ICODE.LOGIN_USER_IS_DISABLED.getMsg());
			}
		}
	}

	/**
	 * 校验token过期，无过期状态。
	 */
	private class DefaultPostAuthenticationChecks implements UserDetailsChecker {
		public void check(UserDetails user) {
			if (!user.isCredentialsNonExpired()) {
				log.debug("用户帐户凭据已过期");
				throw new CredentialsExpiredException("用户帐户凭据已过期");
			}
		}
	}

}
